/*
===========================================================================
This source file is part of DyLab (Dynamic Laboratory)
For the latest info, see http://dylab.googlecode.com

Copyright (c) 2006-2008 Lukas Krejci
(krejci.lukas@volny.cz)

This file is part of DyLab.

    DyLab is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DyLab is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DyLab.  If not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/

#ifndef __dylab_TriangleGeometry3_h__
#define __dylab_TriangleGeometry3_h__

//===========================================================================================
#include <dylab/utils/EnumIterator.h>
#include <dylab/utils/String.h>
#include <dylab/utils/geometry/Plane.h>
#include <dylab/utils/exceptions/ENumericError.h>
#include <dylab/utils/exceptions/EInvalidArgument.h>
#include <dylab/utils/exceptions/EBreak.h>

//===========================================================================================
namespace dylab {

    /** 
     */
    class DYLAB_EXPORT TriangleGeometry3 
    {
    public:
        ///
        enum vertexidx_t
        {
            V_0 = 0,
            V_1,
            V_2
        };
        ///
        class DYLAB_EXPORT VertexIdxIterator : public EnumIterator<vertexidx_t, V_0, V_2> { };
        ///
        enum { V_COUNT = V_2 + 1 };
        ///
        enum edgeidx_t
        {
            E_0 = 0,
            E_1,
            E_2
        };
        ///
        class DYLAB_EXPORT EdgeIdxIterator : public EnumIterator<edgeidx_t, E_0, E_2> { };
        ///
        enum { E_COUNT = E_2 + 1 };
        //-----------------------------------------------------------------------------
        //
        struct DYLAB_EXPORT FeatureIdx
        {
            ///
            union 
            {
                vertexidx_t vertexIdx;
                edgeidx_t   edgeIdx;
            };
            ///
            FeatureIdx()
                    { }
            ///
            FeatureIdx(vertexidx_t _vertexIdx)
                : vertexIdx(_vertexIdx)
                    { }
            ///
            FeatureIdx(edgeidx_t _edgeIdx)
                : edgeIdx(_edgeIdx)
                    { }
        };
        //-----------------------------------------------------------------------------
        class DYLAB_EXPORT pointcls_t 
            : public std::pair<Math::pointcls_t, FeatureIdx>
        { 
        public:
            pointcls_t(Math::pointcls_t pointCls, FeatureIdx featureIdx)
                : std::pair<Math::pointcls_t, FeatureIdx>(pointCls, featureIdx)
                    { }
        };
        //-----------------------------------------------------------------------------
    public:
        /**
        */
        static void getEdgeVertexIndices(edgeidx_t _edgeIdx, 
			vertexidx_t (&_vertexIndices)[EdgeGeometry3::V_COUNT]);
    private:       
        ///
        Vector3 mVertices[V_COUNT];
    public:

        /**          
         */
        TriangleGeometry3() 
                { }

        /**
        */
        TriangleGeometry3(const Vector3 & _v0, const Vector3 & _v1, const Vector3 & _v2)             
                { mVertices[V_0] = _v0, mVertices[V_1] = _v1, mVertices[V_2] = _v2; }

        /**
        */
        TriangleGeometry3(const Vector3 (&_vertices)[V_COUNT])             
                { for (VertexIdxIterator vertexIdx; vertexIdx.isValid(); vertexIdx++) mVertices[vertexIdx] = _vertices[vertexIdx]; }

        /**
        */
        TriangleGeometry3(const TriangleGeometry3 & _triangle) 
                { for (VertexIdxIterator vertexIdx; vertexIdx.isValid(); vertexIdx++) mVertices[vertexIdx] = _triangle.mVertices[vertexIdx]; }

        /** 
         */
        Vector3 & vertex(vertexidx_t _vertexIdx) 
                { return mVertices[_vertexIdx]; }

        /** 
         */
        const Vector3 & vertex(vertexidx_t _vertexIdx) const
                { return mVertices[_vertexIdx]; }

        /**
        */
        Vector3 & operator[] (vertexidx_t _vertexIdx) 
                { return mVertices[_vertexIdx]; }
        
        /**
        */
        const Vector3 & operator[] (vertexidx_t _vertexIdx) const
                { return mVertices[_vertexIdx]; }

        /**
        */
        Vector3 & operator[] (uint32_t _index) 
                { 
                    DYLAB_ASSERT(_index < V_COUNT);
                    return mVertices[_index]; 
                }
        
        /**
        */
        const Vector3 & operator[] (uint32_t _index) const
                { 
                    DYLAB_ASSERT(_index < V_COUNT);
                    return mVertices[_index]; 
                }

        /**
        */
        Plane getPlane() const
                { return Plane(mVertices[V_0], mVertices[V_1], mVertices[V_2]); }

        /**
        */
        EdgeGeometry3 getEdgeGeometry(edgeidx_t _edgeIdx) const
                {
                    vertexidx_t edgeVertexIndices[EdgeGeometry3::V_COUNT];
                    getEdgeVertexIndices(_edgeIdx, edgeVertexIndices);
                    return EdgeGeometry3(mVertices[edgeVertexIndices[V_0]], mVertices[edgeVertexIndices[V_1]]);
                }

        /**
        */
        bool isDegenerated() const
                { return Math::almostZero(computeArea()); }

        /** 
         */
        Vector3 computeCenter() const
                { 
                    Vector3 cp = Vector3::ZERO;
                    for (VertexIdxIterator vertexIdx; vertexIdx.isValid(); vertexIdx++)
                        cp += vertex(vertexIdx);
                    return cp / V_COUNT; 
                }

        /**
        */
        Vector3 computeNormal() const
                { return (mVertices[V_1] - mVertices[V_0]).cross(mVertices[V_2] - mVertices[V_0]).normalise(); }

        /**
        */
        real_t computeArea() const
                { return (mVertices[V_1] - mVertices[V_0]).cross(mVertices[V_2] - mVertices[V_0]).length() / DYLAB_R(2); }

        /**
        */
        Vector3 computeBarycentricCoords(const Vector3 & _p) const;

        /**
        */
        pointcls_t classifyPoint(const Vector3 & _p) const;

        /**
        */
        Math::intersection_t computeLineIntersection(const Vector3 & _a, const Vector3 & _b, 
			Vector3 & _ip = Vector3(), bool allowAMinus = true, bool allowBPlus = true) const;

        /** 
         */
        Math::intersection_t computeRayIntersection(const Ray3 & _ray, Vector3 & _ip = Vector3()) const
                { return computeLineIntersection(_ray.start(), _ray.start() + _ray.direction(), _ip, false, true); }

        /** 
         */
        Math::intersection_t computeEdgeIntersection(const EdgeGeometry3 & _edge, Vector3 & _ip = Vector3()) const
                { return computeLineIntersection(_edge.vertex(EdgeGeometry3::V_0), _edge.vertex(EdgeGeometry3::V_1), _ip, false, false); }

        /**
        */
        operator String() const                
                { return String::fmt("[%1%, %2%, %3%]", mVertices[V_0], mVertices[V_1], mVertices[V_2]); }
    };

    //===========================================================================================    
    inline std::ostream & operator << (std::ostream & _os, const TriangleGeometry3 & _o)
			{ return _os << String(_o); }

    //===========================================================================================    
    DYLAB_EXPORT std::ostream & operator << (std::ostream & _os, const TriangleGeometry3::pointcls_t & _pointCls);
}
//===========================================================================================
#endif // __dylab_TriangleGeometry3_h__
